package com.detech.sbmh.base.aspect;

import cn.hutool.json.JSONUtil;
import com.detech.sbmh.base.annotation.Log;
import com.detech.sbmh.base.enumtype.LogTypeEnum;
import com.detech.sbmh.base.hibernate.log.entity.TbLogSys;
import com.detech.sbmh.base.hibernate.user.entity.TbUser;
import com.detech.sbmh.base.log.properties.LogProperties;
import com.detech.sbmh.base.log.service.LogService;
import com.detech.sbmh.base.redis.RedisUtils;
import com.detech.sbmh.base.util.SpringContextUtils;
import com.detech.sbmh.base.util.UserFun;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 系统日志注解实现类
 */
@Aspect
@Component
public class LogAspect {

    @Resource
    private LogService logService;

    private Logger log = LoggerFactory.getLogger("root");

    @Pointcut("@annotation(com.detech.sbmh.base.annotation.Log)")
    public void logPointCut() {

    }

    //保存操作日志
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //保存操作日志
        saveSysLog(point,null);
        //执行方法
        return point.proceed();
    }

    /**
     * 异常处理（记录异常日志）
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void aroundThrowing(JoinPoint joinPoint, Exception e){
        saveSysLog(joinPoint,e);
    }

    private void saveSysLog(JoinPoint joinPoint,Exception e) {
        LogProperties logProperties = SpringContextUtils.getBean(LogProperties.class);
        boolean log_record_enable = Boolean.parseBoolean(logProperties.getLog_record_enable());
        if(!log_record_enable){
            return ;
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取request
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        HttpServletResponse response = servletRequestAttributes.getResponse();

        TbLogSys sysLogEntity = new TbLogSys();
        //根据token，从Redis获取用户信息
        String token = request.getHeader("token");
        if(token!=null){
            TbUser user = RedisUtils.getCacheObject(token);
            if(user!=null){
                MDC.put("user",user.getUsername()); //注入日志属性值 user
                sysLogEntity.setOperator(user.getUsername());
            }
        }
        Log syslog = method.getAnnotation(Log.class);
        //@SysLog 注解上的属性
        sysLogEntity.setContent(syslog.value());
        sysLogEntity.setModule(syslog.model().name());
        if(e!=null){
            //有异常发生时，日志级别固定为ERROR
            sysLogEntity.setLogType(LogTypeEnum.ERROR.name());
        }else{
            sysLogEntity.setLogType(syslog.logType().name());
        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLogEntity.setMethodUrl(className + "." + methodName + "()");
        //请求的参数
        Object[] args = joinPoint.getArgs();
        final String[] parameterNames = signature.getParameterNames();
        final Class[] parameterTypes = signature.getParameterTypes();
        String params;
        try {
            String showParamValue = null;
            String showParam = syslog.showParam();
            String[] showParamSz = null;
            if(showParam!=null && showParam.length()>0){
                showParamSz = showParam.split(",");
            }
            List<Object> paras = new ArrayList<>();
            for(int i=0; i<parameterNames.length; i++){
                if("request".equals(parameterNames[i]) || "response".equals(parameterNames[i])){
                    continue;
                }
                String packageName = getPackageName(parameterTypes[i]);
                //自定义实体参数
                if(packageName!=null && !packageName.startsWith("java.lang")){
                    String param = JSONUtil.toJsonStr(args[i]);
                    paras.add(param);
                    if(showParamSz!=null && showParamSz.length>0){
                        for(int m=0;m<showParamSz.length;m++){
                            if(showParamValue==null){
                                showParamValue = showParamValue(args[i],showParamSz[m]);
                            }else{
                                showParamValue = showParamValue + " | " + showParamValue(args[i],showParamSz[m]);
                            }
                        }
                    }
                }
                //Java基本类型参数
                else{
                    String paramValue = parameterNames[i]+":"+args[i];
                    paras.add(paramValue);
                    if(showParamSz!=null && showParamSz.length>0) {
                        for (int m = 0; m < showParamSz.length; m++) {
                            if(parameterNames[i].equals(showParamSz[m])){
                                if(showParamValue==null){
                                    showParamValue = paramValue;
                                }else{
                                    showParamValue = showParamValue + " | " + paramValue;
                                }
                            }
                        }
                    }
                }
            }
            params = JSONUtil.toJsonStr(paras);
            if(showParam!=null && showParam.trim().length()>0){
                if(showParamValue!=null){
                    MDC.put("showParam",showParamValue);//注入日志属性值 showParam
                }else{
                    MDC.put("showParam","方法参数【"+syslog.showParam()+"】的值全部为空！");//注入日志属性值 showParam
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            params = "日志转换请求参数Json转换异常!";
        }
        sysLogEntity.setMethodParams(params);
        //设置IP地址
        sysLogEntity.setIp(UserFun.getIpAddr(request));
        sysLogEntity.setLogTime(new Date());
        sysLogEntity.setId(UserFun.getUUID32());

        if(e==null){
            if("DEBUG".equals(syslog.logType().name())){
                log.debug(syslog.value());
            }else if("INFO".equals(syslog.logType().name())){
                log.info(syslog.value());
            }else if("WARN".equals(syslog.logType().name())){
                log.warn(syslog.value());
            }else if("ERROR".equals(syslog.logType().name())){
                log.error(syslog.value(),e);
            }
        }
        MDC.remove("user");
        MDC.remove("showParam");

        if(e!=null){
            //根据异常对象，取得异常堆栈信息（取前面500行）
            String stackTrace = UserFun.getStackTraceForHtml(e, 500);
            sysLogEntity.setStackTrace(stackTrace);
        }

        //保存系统日志到数据库
        if (syslog.saveDB() || e != null) {
            logService.saveSysLog(sysLogEntity);
        }

    }

    private String showParamValue(Object arg0,String showParam){
        String paramValue = null;
        try{
            if(showParam==null || showParam.trim().length()==0){
                return paramValue;
            }
            int newPointIndex = showParam.indexOf(".");
            if(newPointIndex>0){
                //利用Java反射机制，获取实体字段的数据
                int oldPointIndex = -1;
                Object objTmp = arg0;
                while (newPointIndex>0){
                    String filedName = showParam.substring(oldPointIndex+1,newPointIndex);
                    Object obj = UserFun.doGetMethod(objTmp,filedName);
                    if(obj!=null){
                        if(obj.getClass().isPrimitive()==false){
                            oldPointIndex = newPointIndex;
                            objTmp = obj;
                            newPointIndex = showParam.indexOf(".",newPointIndex+1);
                            if(newPointIndex<0){
                                filedName = showParam.substring(oldPointIndex+1,showParam.length());
                                Object objLast = UserFun.doGetMethod(objTmp,filedName);
                                if(objLast!=null){
                                    paramValue = showParam+":"+objLast.toString();
                                }else{
                                    break;
                                }
                            }
                        }else{
                            break;
                        }
                    }else{
                        break;
                    }
                }
            }else{
                Object paramVal = UserFun.doGetMethod(arg0,showParam);
                if(paramVal!=null){
                    paramValue = showParam+":"+paramVal.toString();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return paramValue;
    }

    public String getPackageName(Class a){
        String packageName = null;
        try {
            packageName = a.getPackage().getName();
        }catch (Exception e){
        }
        return packageName;
    }
}
